package com.mzkj.wanjulaile.mobile.api.modules.weChat.service;

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Maps;
import com.mzkj.common.utils.DateUtils;
import com.mzkj.common.utils.HttpUtils;
import com.mzkj.common.utils.StringUtils;
import com.mzkj.wanjulaile.mobile.api.common.service.ServiceException;
import com.mzkj.wanjulaile.mobile.api.modules.account.entity.BuyVip;
import com.mzkj.wanjulaile.mobile.api.modules.account.mapper.BuyVipMapper;
import com.mzkj.wanjulaile.mobile.api.modules.order.constant.Constant;
import com.mzkj.wanjulaile.mobile.api.modules.order.entity.Order;
import com.mzkj.wanjulaile.mobile.api.modules.order.mapper.OrderMapper;
import com.mzkj.wanjulaile.mobile.api.modules.sys.entity.SystemKeyInfo;
import com.mzkj.wanjulaile.mobile.api.modules.sys.service.SystemKeyInfoService;
import com.mzkj.wanjulaile.mobile.api.modules.weChat.entity.*;
import com.mzkj.wanjulaile.mobile.api.modules.weChat.mapper.NotifyReturnMapper;
import com.mzkj.wanjulaile.mobile.api.modules.weChat.utils.WechatUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.net.ssl.SSLContext;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyStore;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * Copyright © 2016 上海美挚网络. All rights reserved.
 *
 * @Projec: meilein
 * @Package:in.meile.print.modules.mobile.service
 * @Description:微信支付服务类
 * @author:bing
 * @date:16/5/6 上午10:04
 * @version:V1.0
 */
@Service("weChatPayService")
public class WechatPayService {

    public static final Logger logger = Logger.getLogger(WechatPayService.class);

    @Autowired
    private SystemKeyInfoService systemKeyInfoService;
    @Autowired
    private NotifyReturnService notifyReturnService;
    @Autowired
    private NotifyReturnMapper notifyReturnDao;
    @Autowired
    private OrderMapper orderDao;
    @Autowired
    private BuyVipMapper buyVipDao;

    private final String FILL_STR = "000000";

    /**
     * 微信支付统一下单
     *
     * @param order 业务订单数据
     * @param map   其他支付参数
     * @returnC
     */
    public SortedMap<String, Object> unifiedOrder(Order order, Map<String, Object> map) {
        SortedMap<String, Object> resultMap;
        try {
            String openId = (String) map.get("openId");
            String remoteIp = (String) map.get("remoteIp");
            WxPaySendData wxPaySendData = new WxPaySendData();
            //构建微信支付请求参数集合
            wxPaySendData.setAppId(WxConstant.APP_ID);
            wxPaySendData.setAttach("玩具来了-订单支付:" + order.getOrderNumber());
            wxPaySendData.setBody("玩具来了-订单支付");
            wxPaySendData.setMchId(WxPayConfig.MCH_ID);
            wxPaySendData.setNonceStr(WechatUtils.getRandomStr(32));
            wxPaySendData.setNotifyUrl(WxPayConfig.NOTIFY_URL);
            wxPaySendData.setDeviceInfo("APP_API");
            wxPaySendData.setOutTradeNo(order.getOrderNumber());
            wxPaySendData.setTotalFee(getTotalFee(order));
//            wxPaySendData.setTradeType(WxPayConfig.TRADE_TYPE_JSAPI);
            wxPaySendData.setTradeType(WxPayConfig.TRADE_TYPE_APP);
            wxPaySendData.setSpBillCreateIp(remoteIp);
//            wxPaySendData.setOpenId(openId);
            SortedMap<String, Object> paramters = buildParamMap(wxPaySendData);
            wxPaySendData.setSign(WechatUtils.getSign(paramters));
            String reqXml = WechatUtils.wxSendDataToXml(wxPaySendData);
            System.out.println(reqXml);
            logger.info("微信支付统一下单请求参数 START==》");
            logger.info(reqXml);
            logger.info("微信支付统一下单请求参数 END《==");
            byte[] xmlData = reqXml.getBytes();
            URL url = new URL(WxPayConfig.UNIFIED_ORDER_URL);
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.setDoOutput(true);
            urlConnection.setDoInput(true);
            urlConnection.setUseCaches(false);
            urlConnection.setRequestProperty("Content_Type", "text/xml");
            urlConnection.setRequestProperty("Content-length", String.valueOf(xmlData.length));
            DataOutputStream outputStream = new DataOutputStream(urlConnection.getOutputStream());
            outputStream.write(xmlData);
            outputStream.flush();
            outputStream.close();
            resultMap = WechatUtils.parseXml(urlConnection.getInputStream());
        } catch (Exception e) {
            logger.error("微信支付统一下单异常", e);
            throw new RuntimeException("微信支付统一下单异常", e);
        }
        return resultMap;
    }

    /**
     * 微信支付统一下单
     *
     * @param buyVip 购买记录
     * @param map    其他支付参数
     * @return
     */
    public SortedMap<String, Object> unifiedVip(BuyVip buyVip, Map<String, Object> map) {
        SortedMap<String, Object> resultMap;
        try {
            String openId = (String) map.get("openId");
            String remoteIp = (String) map.get("remoteIp");
            WxPaySendData wxPaySendData = new WxPaySendData();
            //构建微信支付请求参数集合
            wxPaySendData.setAppId(WxConstant.APP_ID);
            //附加数据
            wxPaySendData.setAttach(buyVip.getVipScheme().getTypeName());
            wxPaySendData.setBody("玩具来了-会员购买");
            wxPaySendData.setMchId(WxPayConfig.MCH_ID);
            wxPaySendData.setNonceStr(WechatUtils.getRandomStr(32));
            wxPaySendData.setNotifyUrl(WxPayConfig.VIP_NOTIFY_URL);
            wxPaySendData.setDeviceInfo("APP_API");
            //商户系统内部的订单号 重新发起一笔支付要使用原订单号，避免重复支付；已支付过或已调用关单、撤销的订单号不能重新发起支付
            wxPaySendData.setOutTradeNo(buyVip.getPayNumber());
            wxPaySendData.setTotalFee(buyVip.getFee() + buyVip.getDeposit() - buyVip.getCouponVal());
//            wxPaySendData.setTradeType(WxPayConfig.TRADE_TYPE_JSAPI);
            wxPaySendData.setTradeType(WxPayConfig.TRADE_TYPE_APP);
            wxPaySendData.setSpBillCreateIp(remoteIp);
//            wxPaySendData.setOpenId(openId);
            SortedMap<String, Object> paramters = buildParamMap(wxPaySendData);
            wxPaySendData.setSign(WechatUtils.getSign(paramters));
            String reqXml = WechatUtils.wxSendDataToXml(wxPaySendData);
            System.out.printf("微信支付统一下单请求参数:" + reqXml);
            logger.info("微信支付统一下单请求参数 START==》");
            logger.info(reqXml);
            logger.info("微信支付统一下单请求参数 END《==");
            byte[] xmlData = reqXml.getBytes();
            URL url = new URL(WxPayConfig.UNIFIED_ORDER_URL);
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.setDoOutput(true);
            urlConnection.setDoInput(true);
            urlConnection.setUseCaches(false);
            urlConnection.setRequestProperty("Content_Type", "text/xml");
            urlConnection.setRequestProperty("Content-length", String.valueOf(xmlData.length));
            DataOutputStream outputStream = new DataOutputStream(urlConnection.getOutputStream());
            outputStream.write(xmlData);
            outputStream.flush();
            outputStream.close();
            resultMap = WechatUtils.parseXml(urlConnection.getInputStream());
        } catch (Exception e) {
            logger.error("微信支付统一下单异常", e);
            throw new RuntimeException("微信支付统一下单异常", e);
        }
        return resultMap;
    }

    /**
     * 生成支付参数
     *
     * @param map 微信支付统一下单返回参数
     * @return 支付参数
     */
    public SortedMap<String, Object> createPayParam(SortedMap<String, Object> map) {
        SortedMap<String, Object> signMap = Maps.newTreeMap();
        String appId = (String) map.get("appid");
        String partnerid = (String) map.get("mch_id");
        String prepayid = (String) map.get("prepay_id");
        String timeStamp = WechatUtils.getTimeStamp();
        String nonceStr = WechatUtils.getRandomStr(20);
        signMap.put("appid", appId);
        signMap.put("partnerid", partnerid);
        signMap.put("prepayid", prepayid);
        signMap.put("package", "Sign=WXPay");
        signMap.put("noncestr", nonceStr);
        signMap.put("timestamp", timeStamp);
        String paySign = WechatUtils.getSign(signMap);
        signMap.put("packagee", "Sign=WXPay");
        signMap.remove("package");
        signMap.put("sign", paySign);
        return signMap;
    }

    /**
     * 申请退款
     *  @param outTradeNo
     * @param map
     * @param type 订单退运费或 buyvip退押金
     */
    public NotifyReturnData refund(String outTradeNo, Map<String, Object> map, String type) {
        NotifyReturnData returnData = null;
        //获取微信支付通知结果
        NotifyReturnData notifyData = notifyReturnService.getByOutTradeNo(outTradeNo);
        SortedMap<String, Object> resultMap = Maps.newTreeMap();
        FileInputStream inputStream = null;
        try {
            if (null != notifyData) {
                String appId = notifyData.getAppid();
                String mchId = notifyData.getMch_id();
                WxPaySendData refundData = new WxPaySendData();
                refundData.setAppId(appId);
                refundData.setMchId(mchId);
                refundData.setNonceStr(WechatUtils.getRandomStr(20));
                refundData.setTransactionId(notifyData.getTransaction_id());
                //refundData.setOutTradeNo(order.getOrderNumber());
                refundData.setOutRefundNo(getBatchNo());
                //后续可能增加代金券或者立减优惠活动[退款金额=总费用-代金券金额]
                refundData.setTotalFee(notifyData.getTotal_fee());
                refundData.setRefundFee(notifyData.getTotal_fee());
                refundData.setOpUserId(mchId);
                SortedMap<String, Object> paramMap = buildParamMap(refundData);
//                refundData.setSign(WechatUtils.getSign(paramMap));
                refundData.setSign(WechatUtils.getSignByResource(paramMap,notifyData.getDevice_info()));
                String reqXml = WechatUtils.wxSendDataToXml(refundData);
                System.out.println(reqXml);
                //加载证书
                KeyStore keyStore = KeyStore.getInstance("PKCS12");

                String CERT_PATH = WxPayConfig.CERT_PATH;
                if(WxPayConfig.RESOURCE_PAY_WEB.equals(notifyData.getDevice_info())){
                    CERT_PATH = WxPayConfig.WEB_CERT_PATH;
                }
                inputStream = new FileInputStream(new File(CERT_PATH + appId + "apiclient_cert.p12"));
                keyStore.load(inputStream, mchId.toCharArray());

                SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(keyStore, mchId.toCharArray()).build();
                SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new String[]{"TLSv1"}, null,
                        SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
                CloseableHttpClient httpclient = HttpClients.custom()
                        .setSSLSocketFactory(sslsf)
                        .build();
                //申请退款
                HttpPost httpPost = new HttpPost(WxPayConfig.REFUND_URL);
                StringEntity stringEntity = new StringEntity(reqXml);
                httpPost.setEntity(stringEntity);
                System.out.println("executing request" + httpPost.getRequestLine());
                CloseableHttpResponse response = httpclient.execute(httpPost);
                if (response.getStatusLine().getStatusCode() == 200) {
                    HttpEntity entity = response.getEntity();
                    if (entity != null) {
                        resultMap = WechatUtils.parseXml(entity.getContent());
                    }
                    EntityUtils.consume(entity);
                    response.close();
                    httpclient.close();
                    //解析返回结果
                    ObjectMapper om = new ObjectMapper();
                    JSONObject jsonObject = new JSONObject(resultMap);
                    returnData = om.readValue(jsonObject.toString(), NotifyReturnData.class);
                    //验证返回结果是否由微信发出
                    boolean chackRs = WechatUtils.checkResponseSign(resultMap);
                    if (WxConstant.RETURN_SUCCESS.equals(resultMap.get("return_code"))) {
                        if (!chackRs) {
                            returnData = new NotifyReturnData();
                            returnData.setResult_code("FAIL");
                            returnData.setErr_code("FAIL");
                            returnData.setErr_code_des("返回结果并非来自微信");
                            logger.error("返回结果并非来自微信");
                        }
                    }
                } else {
                    returnData = new NotifyReturnData();
                    returnData.setReturn_code("FAIL");
                    returnData.setReturn_msg("请求异常");
                    logger.error("申请微信退款请求异常");
                }
            }
        } catch (Exception e) {
            logger.error("申请微信退款异常", e);
            throw new ServiceException("申请微信退款异常", e);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
        return returnData;
    }

    public void syncRefundStatus(NotifyReturnData notifyData) {
        WxRefundData refundData = new WxRefundData();
        refundData.setAppId(notifyData.getAppid());
        refundData.setDeviceInfo("platform_refund");
        refundData.setMchId(notifyData.getMch_id());
        refundData.setNonceStr(WechatUtils.getRandomStr(20));
        refundData.setTransactionId(notifyData.getTransaction_id());
        SortedMap<String, Object> paramMap = buildParamMap(refundData);
        refundData.setSign(WechatUtils.getSign(paramMap));
        String reqXml = WechatUtils.wxSendDataToXml(refundData);
        try {
            StringEntity stringEntity = new StringEntity(reqXml);
            String result = HttpUtils.doPost(WxPayConfig.REFUND_QUERY_URL, stringEntity);
            SortedMap<String, Object> resultMap = WechatUtils.parseXml(result);
            String returnCode = (String) resultMap.get("return_code");
            if (WxConstant.RETURN_SUCCESS.equals(returnCode)) {
                String resultCode = (String) resultMap.get("result_code");
                if (WxConstant.RESULT_SUCCESS.equals(resultCode)) {
                    String refundStatus = (String) resultMap.get("refund_status_0");
                    updateRefundStatus(notifyData, refundStatus);
                } else {
                    String errCodeDes = (String) resultMap.get("err_code_des");
                    logger.error("out_refund_no --> " + notifyData.getOut_refund_no());
                    logger.error(resultCode + " --> " + errCodeDes);
                }
            } else {
                String returnMsg = (String) resultMap.get("return_msg");
                logger.error("out_refund_no --> " + notifyData.getOut_refund_no());
                logger.error(returnCode + " --> " + returnMsg);
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }

    private void updateRefundStatus(NotifyReturnData notifyData, String refundStatus) {
        if (StringUtils.isNotEmpty(refundStatus) && !refundStatus.equals(notifyData.getRefund_status())) {
            NotifyReturnData notifyReturnData2 = new NotifyReturnData();
            notifyReturnData2.setId(notifyData.getId());
            notifyReturnData2.setRefund_status(refundStatus);
            //更新退款记录状态
            notifyReturnDao.updateRefundStatus(notifyReturnData2);
            //更新退款管理显示的退款记录状态
            NotifyReturnData notifyReturnData3 = notifyReturnDao.getByOutTradeNo(notifyData.getOut_trade_no());
            notifyReturnData3.setRefund_status(refundStatus);
            notifyReturnDao.updateRefundStatus(notifyReturnData3);
            // 更新订单退款状态
            if ("SUCCESS".equals(refundStatus)) {
                Order order = orderDao.getByOrderNumber(notifyData.getOut_trade_no());
                if (order != null){
                    order.setPayStatus(Constant.PAYSTATUS_RETURNED);
                    orderDao.update(order);
                }
                BuyVip buyVip = buyVipDao.getByPayNumber(notifyData.getOut_trade_no());
                if (buyVip != null){
                    buyVip.setPayStatus(BuyVip.DEPOSIT_STATUS_RETURNED);
                    buyVip.setRefundStatus(1);
                    buyVipDao.update(buyVip);
                }
            }
            if ("PROCESSING".equals(refundStatus)){
                Order order = orderDao.getByOrderNumber(notifyData.getOut_trade_no());
                if (order != null){
                    order.setPayStatus(Constant.PAYSTATUS_REFUNDING);
                    orderDao.update(order);
                }
                BuyVip buyVip = buyVipDao.getByPayNumber(notifyData.getOut_trade_no());
                if (buyVip != null){
                    buyVip.setRefundStatus(BuyVip.DEPOSIT_STATUS_REFUNDING);
                    buyVipDao.update(buyVip);
                }
            }
        }
    }


    private String getBatchNo() {
        String batchNo = null;
        String dateStr = DateUtils.getDate("yyyyMMdd");
        SystemKeyInfo sysKeyInfo = new SystemKeyInfo();
        sysKeyInfo.setDomain("wx" + dateStr);
        SystemKeyInfo temp = null;
        try {
            temp = systemKeyInfoService.get(sysKeyInfo);
            if (null == temp) {
                sysKeyInfo.setStep(1);
                sysKeyInfo.setCurrentNum(new Long(0));
                sysKeyInfo.setRemark("微信退款批次号前缀");
                sysKeyInfo = systemKeyInfoService.insert(sysKeyInfo);
            } else {
                sysKeyInfo = temp;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        Long curNum = sysKeyInfo.getCurrentNum() + sysKeyInfo.getStep();
        sysKeyInfo.setCurrentNum(curNum);
        systemKeyInfoService.update(sysKeyInfo);
        int length = curNum.toString().length();
        if (length <= 6) {
            batchNo = dateStr + FILL_STR.substring(0, FILL_STR.length() - length) + curNum.toString();
        } else {
            //每天退款应该不会超过999999笔
        }
        return batchNo;
    }


    private SortedMap<String, Object> buildParamMap(WxRefundData data) {
        SortedMap<String, Object> paramters = new TreeMap<String, Object>();
        if (null != data) {
            if (StringUtils.isNotEmpty(data.getAppId())) {
                paramters.put("appid", data.getAppId());
            }
            if (StringUtils.isNotEmpty(data.getDeviceInfo())) {
                paramters.put("device_info", data.getDeviceInfo());
            }
        }
        if (StringUtils.isNotEmpty(data.getMchId())) {
            paramters.put("mch_id", data.getMchId());
        }
        if (StringUtils.isNotEmpty(data.getNonceStr())) {
            paramters.put("nonce_str", data.getNonceStr());
        }
        if (StringUtils.isNotEmpty(data.getOutTradeNo())) {
            paramters.put("out_trade_no", data.getOutTradeNo());
        }
        if (StringUtils.isNotEmpty(data.getSign())) {
            paramters.put("sign", data.getSign());
        }
        if (StringUtils.isNotEmpty(data.getTransactionId())) {
            paramters.put("transaction_id", data.getTransactionId());
        }
        if (StringUtils.isNotEmpty(data.getOutRefundNo())) {
            paramters.put("out_refund_no", data.getOutRefundNo());
        }
        return paramters;
    }

    /**
     * 构建统一下单参数map 用于生成签名
     *
     * @param data
     * @return
     */
    private SortedMap<String, Object> buildParamMap(WxPaySendData data) {
        SortedMap<String, Object> paramters = new TreeMap<String, Object>();
        if (null != data) {
            if (StringUtils.isNotEmpty(data.getAppId())) {
                paramters.put("appid", data.getAppId());
            }
            if (StringUtils.isNotEmpty(data.getAttach())) {
                paramters.put("attach", data.getAttach());
            }
            if (StringUtils.isNotEmpty(data.getBody())) {
                paramters.put("body", data.getBody());
            }
            if (StringUtils.isNotEmpty(data.getDetail())) {
                paramters.put("detail", data.getDetail());
            }
            if (StringUtils.isNotEmpty(data.getDeviceInfo())) {
                paramters.put("device_info", data.getDeviceInfo());
            }
            if (StringUtils.isNotEmpty(data.getFeeType())) {
                paramters.put("fee_type", data.getFeeType());
            }
            if (StringUtils.isNotEmpty(data.getGoodsTag())) {
                paramters.put("goods_tag", data.getGoodsTag());
            }
            if (StringUtils.isNotEmpty(data.getLimitPay())) {
                paramters.put("limit_pay", data.getLimitPay());
            }
            if (StringUtils.isNotEmpty(data.getMchId())) {
                paramters.put("mch_id", data.getMchId());
            }
            if (StringUtils.isNotEmpty(data.getNonceStr())) {
                paramters.put("nonce_str", data.getNonceStr());
            }
            if (StringUtils.isNotEmpty(data.getNotifyUrl())) {
                paramters.put("notify_url", data.getNotifyUrl());
            }
            if (StringUtils.isNotEmpty(data.getOpenId())) {
                paramters.put("openid", data.getOpenId());
            }
            if (StringUtils.isNotEmpty(data.getOutTradeNo())) {
                paramters.put("out_trade_no", data.getOutTradeNo());
            }
            if (StringUtils.isNotEmpty(data.getSign())) {
                paramters.put("sign", data.getSign());
            }
            if (StringUtils.isNotEmpty(data.getSpBillCreateIp())) {
                paramters.put("spbill_create_ip", data.getSpBillCreateIp());
            }
            if (StringUtils.isNotEmpty(data.getTimeStart())) {
                paramters.put("time_start", data.getTimeStart());
            }
            if (StringUtils.isNotEmpty(data.getTimeExpire())) {
                paramters.put("time_expire", data.getTimeExpire());
            }
            if (StringUtils.isNotEmpty(data.getProductId())) {
                paramters.put("product_id", data.getProductId());
            }
            if (data.getTotalFee() > 0) {
                paramters.put("total_fee", data.getTotalFee());
            }
            if (StringUtils.isNotEmpty(data.getTradeType())) {
                paramters.put("trade_type", data.getTradeType());
            }
            //申请退款参数
            if (StringUtils.isNotEmpty(data.getTransactionId())) {
                paramters.put("transaction_id", data.getTransactionId());
            }
            if (StringUtils.isNotEmpty(data.getOutRefundNo())) {
                paramters.put("out_refund_no", data.getOutRefundNo());
            }
            if (StringUtils.isNotEmpty(data.getOpUserId())) {
                paramters.put("op_user_id", data.getOpUserId());
            }
            if (StringUtils.isNotEmpty(data.getRefundFeeType())) {
                paramters.put("refund_fee_type", data.getRefundFeeType());
            }
            if (null != data.getRefundFee() && data.getRefundFee() > 0) {
                paramters.put("refund_fee", data.getRefundFee());
            }
        }
        return paramters;
    }

    private int getTotalFee(Order order) {
        int totalFee = 0;
        int shipFee = order.getShipFee() == null ? 1 : order.getShipFee();
        int couponVal = order.getCouponVal() == null ? 0 : order.getCouponVal();
        totalFee = totalFee + shipFee - couponVal;
        return totalFee;
    }
}
